#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <threads.h>
#include <unistd.h>

_Thread_local int epoll_fd = 0;

int setnonblock(int fd) { return fcntl(fd, F_SETFL, O_NONBLOCK); }

int wo_poller_init(void) {
  epoll_fd = epoll_create1(0);
  return epoll_fd;
}

int wo_poller_add(int fd, uint32_t events) {
  struct epoll_event ev;
  ev.events = events;
  ev.data.fd = fd;
  return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
}

int wo_fatal(const char *message) {
  perror(message);
  exit(EXIT_FAILURE);
}

int main(void) {
  // initialize socket server
  int socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (socket_fd == -1) {
    wo_fatal("socket fail");
  }

  struct sockaddr_in in = {
      .sin_zero = {0},
      .sin_family = AF_INET,
      .sin_port = htons(9889),
      .sin_addr = {.s_addr = inet_addr("127.0.0.1")},
  };

  if (bind(socket_fd, (struct sockaddr *)&in, sizeof(in)) == -1)
    wo_fatal("bind fail");

  if (listen(socket_fd, 1024) == -1)
    wo_fatal("listen fail");

  // initialize epoll fd
  if (wo_poller_init() == -1) {
    wo_fatal("wo_poller fail");
  }

  if (wo_poller_add(socket_fd, EPOLLIN) == -1) {
    wo_fatal("wo_poll fail");
  }

  for (;;) {
    struct epoll_event events[10];
    int nfds = epoll_wait(epoll_fd, events, 10, -1);
    if (nfds == -1) {
      wo_fatal("epoll_wait fail");
    }
    printf("epoll_wait nfds %d\n", nfds);

    for (int n = 0; n < nfds; n++) {
      struct sockaddr_in peer_addr;
      socklen_t peer_addr_len = 0;
      memset(&peer_addr, 0, sizeof(peer_addr));
      if (events[n].data.fd == socket_fd) {
        int conn_sock =
            accept(socket_fd, (struct sockaddr *)&peer_addr, &peer_addr_len);
        if (conn_sock == -1) {
          wo_fatal("accept fail");
        }

        if (setnonblock(conn_sock) == -1) {
          wo_fatal("setnonblock fail");
        }

        if (wo_poller_add(conn_sock, EPOLLIN | EPOLLET) == -1) {
          wo_fatal("wo_poller_add fail");
        }
      } else {
        void *buf = malloc(4096);
        ssize_t received = recv(events[n].data.fd, buf, 4096, 0);
        if (received == -1) {
          perror("recv fail");
          exit(EXIT_FAILURE);
        }
        free(buf);
        printf("sock %d received %ld bytes\n", events[n].data.fd,
               (long)received);
      }
    }
  }

  return 0;
}
